#include <compiler.h>
/******************************************************************************
 * Description        : 将USB键盘、鼠标转接至PS/2接口与PC通信
 *                      通过开关切换设备功能（转换键盘或转换鼠标）
 *
 * LED状态：
 *       红色点亮：PS/2端enable
 *       红色点灭：PS/2端disable
 *       红色闪烁：PS/2端通信
 *       蓝色点亮：USB设备识别
 *       蓝色点灭：未插入USB设备
 *       蓝色慢闪：未知USB设备
 ********************************************************************************/
#include <CH549_DEBUG.h>
#include <CH549_UART.h>
#include <CH549_sdcc.h>
#include <HID.H>
#include <USBHOST.h>
#include <key_conv.h>
#include <ps2.H>
#include <ps2_usb-kbms.h>

/*获取设备描述符*/
UINT8C SetupGetDevDescr[] = {USB_REQ_TYP_IN, USB_GET_DESCRIPTOR, 0x00, USB_DESCR_TYP_DEVICE, 0x00, 0x00, sizeof(USB_DEV_DESCR), 0x00};
/*获取配置描述符*/
UINT8C SetupGetCfgDescr[] = {USB_REQ_TYP_IN, USB_GET_DESCRIPTOR, 0x00, USB_DESCR_TYP_CONFIG, 0x00, 0x00, 0x04, 0x00};
/*设置USB地址*/
UINT8C SetupSetAddr[] = {USB_REQ_TYP_OUT, USB_SET_ADDRESS, USB_DEVICE_ADDR, 0x00, 0x00, 0x00, 0x00, 0x00};
/*设置USB配置*/
UINT8C SetupSetConfig[] = {USB_REQ_TYP_OUT, USB_SET_CONFIGURATION, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
/*设置USB接口配置*/
UINT8C SetupSetInterface[] = {USB_REQ_RECIP_INTERF, USB_SET_INTERFACE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
/*清除端点STALL*/
UINT8C SetupClrEndpStall[] = {USB_REQ_TYP_OUT | USB_REQ_RECIP_ENDP, USB_CLEAR_FEATURE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
/*获取HUB描述符*/
UINT8C SetupGetHubDescr[] = {HUB_GET_HUB_DESCRIPTOR, HUB_GET_DESCRIPTOR, 0x00, USB_DESCR_TYP_HUB, 0x00, 0x00, sizeof(USB_HUB_DESCR), 0x00};
/*获取HID设备报表描述符*/
UINT8C SetupHIDGetReportDescr[] = {0x81, USB_GET_DESCRIPTOR, 0x00, USB_DESCR_TYP_REPORT, 0x00, 0x00, 0xFF, 0x00};
/*获取HID设备协议（启动协议或报表协议）*/
UINT8C SetupHIDGetProtocol[] = {0xA1, HID_GET_PROTOCOL, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00};
/*设置 HID空闲不发包 */
UINT8C SetupHIDSetIdle[] = {0x21, HID_SET_IDLE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
/*设置 SetReport */
UINT8C SetupHIDSetReport[] = {0x21, HID_SET_REPORT, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00};

UINT8 ps2_dataBuf; // 发送/接收的数据
UINT8 ps2_dataChk; // 奇偶校验
UINT8 ps2_dataBit; // 发送/接收的数据位
UINT8 Set_Port = 0;
UINT8 ledCnt;   // led熄灭延时（ms）
UINT8 ledBlink; // led闪烁延时（ms）

__xdata __at(0x0148) _RootHubDev ThisUsbDev; // ROOT口
__bit RootHubId;                             // 当前正在操作的root-hub端口号:0=HUB0,1=HUB1
__bit FoundNewDev;                           // 是否有USB设备插入
__bit keyboard_mode = 0;                     //  0: 美式键盘   1: 日式键盘
__bit ps2_dev = 0;                           //  0: 鼠标   1: 键盘
__bit bBoot = 1;                             // 是否刚启动
__bit isPS2Comm = 0;                         // 是否在PS/2通信

// USB 数据缓冲
__xdata __at(0x0000) UINT8V TxBuffer[MAX_PACKET_SIZE]; // OUT, must even address
__xdata __at(0x0040) UINT8V RxBuffer[MAX_PACKET_SIZE]; // IN, must even address
__xdata __at(0x0200) UINT8 Com_Buffer[COM_BUF_SIZE];   // 不能配置在0x0000
__xdata __at(0x0143) UINT8 Com_BufferLen;

// PS/2 数据
__xdata __at(0x0300) UINT8 PS2_TXBuffer[256];         // 发送数据缓冲
__xdata __at(0x0400) UINT8 PS2_KBMS_INFO_AREA[0x180]; // 保存键盘或鼠标的状态 —— 用于同步USB端和PS/2端，让PS/2端一直以为USB端有连接
__xdata __at(0x0400) _PS2_KB_INFO PS2_KB_INFO;        // 保存键盘的状态
__xdata __at(0x0400) _PS2_MS_INFO PS2_MS_INFO;        // 保存鼠标的状态

// HID报表
// 这个0x0600是首地址，后面依次放着若干REPORT_MAP数据快(报表描述符解析结果)
__xdata __at(0x0600) UINT8 REPORT_MAP_AREA[512]; // 锁定区域（防止被占）

__xdata __at(0x0140) UINT8 CUSTOM_AREA[192];
__xdata __at(0x0140) UINT8 PS2_TX_S;     // 发送缓冲开始位置
__xdata __at(0x0141) UINT8 PS2_TX_E;     // 发送缓冲结束位置
__xdata __at(0x0142) UINT8 PS2_RXBuffer; // 接收数据缓冲

__xdata __at(0x0015A) UINT8 USBTransact_endp_pid;
__xdata __at(0x0015B) UINT8 USBTransact_tog;
__xdata __at(0x0015C) UINT16 USBTransact_timeout;
__xdata __at(0x00160) UINT32 wake_timeout; // 计时：用于每个一定时间发送按键或移动指令，防止PC睡眠或黑屏
/*******************************************************************************
 * Description    : 开始监听HID设备，使用定时器Timer2轮询
 * Input          : None
 * Output         : None
 * Return         : None
 *******************************************************************************/
void startListenHID() {
    // 为每个端点初始化轮询计数器，使其不在同一周期触发
    ThisUsbDev.HIDbIntervalCnt = ThisUsbDev.HIDbInterval;

    // Fsys = 24MHz
    C_T2 = 0;            // T2使用内部时钟
    RCAP2H = TH2 = 0xF8; // 1ms触发
    RCAP2L = TL2 = 0x30;
    ET2 = 1; // 允许定时器2中断
    TR2 = 1; // 启动定时器2
    // PT2 = 1;     // 高优先级( > IE_UART1)
}

/*******************************************************************************
 * Description    : 定时器Timer2中断函数，1ms触发, 用于扫描键盘或鼠标定时、PS/2机打定时
 * Input          : None
 * Output         : None
 * Return         : None
 *******************************************************************************/
void Timer2(void) __interrupt INT_NO_TMR2 __using 2 {
    // 清中断标志 (该标志位不会自动清零)
    TF2 = 0;

    // USB 计数
    if (ThisUsbDev.HIDbIntervalCnt) {
        // 如果还没到轮询间隔，则计数器减1后返回
        ThisUsbDev.HIDbIntervalCnt--;
    }

    // PS/2 计数
    if (PS2_KB_INFO.type && PS2_KB_INFO.KeyRepeatCnt) {
        PS2_KB_INFO.KeyRepeatCnt--; // PS/2机打延时计数
    }

    if (wake_timeout) {
        wake_timeout--;
    }

    if (isPS2Comm) {
        isPS2Comm = 0;
        ledCnt = 40;
    }

    if (ledCnt) {
        ledCnt--;
        // blink
        if (ledBlink == 60) {
            led_ps2 = 1;
            ledBlink--;
        } else if (ledBlink == 40) {
            led_ps2 = 0;
            ledBlink--;
        } else if (ledBlink == 0) {
            ledBlink = 60;
        } else {
            ledBlink--;
        }

    } else {
        ledBlink = 60;
        if (PS2_KB_INFO.Enable) {
            // led on
            led_ps2 = 0;
        } else {
            led_ps2 = 1;
        }
    }
}

void main() {
    UINT8 s, endp;
    // 开灯
    led_ps2 = 0;
    led_usb = 0;

    CfgFsys();
    mDelaymS(500);

    // 关灯
    led_ps2 = 1;
    led_usb = 1;

    wake_timeout = 1200000; // 2分钟
#if DE_PRINTF
    // 为了让计算机通过串口监控演示过程
    CH549UART1Init();  // 串口1初始化
    CH549UART1Alter(); // 串口1引脚映射-将串口一映射到 p1.6 和 p1.7  P1.6（UART1引脚映射————RXD1_）（P1.7 UART1引脚映射————TXD1_）
#endif

    printf("Start @ChipID=%02X\n", (UINT16)CHIP_ID);

    ps2_dev = sel_mode; // 选择ps/2设备 —— 只在启动时根据开关位置决定是模拟键盘还是鼠标

    // 初始化USB
    InitUSB_Host();

    // 初始化PS/2 设备
    InitPS2();
    printf("Wait Device In\n");
    EA = 1;

    // 开启定时器2
    startListenHID();

    ThisUsbDev.DeviceType = USB_DEV_CLASS_RESERVED; // 初始化为0(未插入)
    FoundNewDev = 0;

    while (1) {
        s = ERR_SUCCESS;
        if (UIF_DETECT || bBoot) // 如果有USB主机检测中断则处理
        {
            bBoot = 0;
            UIF_DETECT = 0;       // 清中断标志
            s = AnalyzeRootHub(); // 分析ROOT-HUB状态——插入还是拔出
            if (s == ERR_USB_CONNECT) {
                FoundNewDev = 1;
            } else if (s == ERR_USB_DISCON) {
                // 设备拔出
                ThisUsbDev.DeviceType = USB_DEV_CLASS_RESERVED; // 初始化为0(未插入)
                led_usb = 1;
            }
        }

        if (FoundNewDev) // 有新的USB设备插入
        {
            FoundNewDev = 0;
            mDelaymS(200);           // 由于USB设备刚插入尚未稳定,故等待USB设备数百毫秒,消除插拔抖动
            s = EnumAllRootDevice(); // 枚举所有ROOT-HUB端口的USB设备
            if (s != ERR_SUCCESS) {
                printf("EnumAllRootDev err = x%02X\n", (UINT16)s);
            } else {
                // 获取报表描述符
                printf("GetHIDReport: \n");
                s = CtrlHIDGetReportDescr();
                if (s == ERR_SUCCESS) {
                    // 根据报表描述符初始化HID设备
                    hid_init();
                }

                // 根据设备类型进行模拟ps/2
                if (ThisUsbDev.DeviceType == DEV_TYPE_KEYBOARD ||
                    ThisUsbDev.DeviceType == DEV_TYPE_MOUSE) {
                    led_usb = 0;
                    if (ps2_dev) {
                        // 闪灯
                        // 键盘
                        hid_setup(1, HID_LED_NUM_LOCK);
                        mDelaymS(100);
                        hid_setup(1, HID_LED_CAPS_LOCK);
                        mDelaymS(100);
                        hid_setup(1, HID_LED_SCSROLL_LOCK);
                        mDelaymS(100);
                        hid_setup(1, 0x00); // 熄灭所有LED

                        // 根据保存的PS/2设备信息设置USB设备 —— LED
                        hid_setup(1, ps2toHIDCodeLED(PS2_KB_INFO.LED));
                    }

                    printf("begin convert\n");
                }
            }
        }

        if (ThisUsbDev.DeviceType == DEV_TYPE_KEYBOARD ||
            ThisUsbDev.DeviceType == DEV_TYPE_MOUSE) {

            // 扫描键盘或鼠标，转换成PS/2发送
            if (ThisUsbDev.HIDEndPoint && ThisUsbDev.HIDbIntervalCnt == 0) {

                // 端点有效， 并且到达轮询间隔，尝试向USB中断查询
                ThisUsbDev.HIDbIntervalCnt = ThisUsbDev.HIDbInterval;
                endp = ThisUsbDev.HIDEndPoint;

                USBTransact_endp_pid = USB_PID_IN << 4 | endp & 0x7F;
                USBTransact_tog = endp & 0x80 ? bUH_R_TOG | bUH_T_TOG : 0;
                USBTransact_timeout = 0;
                s = USBTransact(); // 传输事务,获取数据,NAK不重试
                if (s == ERR_SUCCESS) {
                    endp ^= 0x80;                  // 同步标志翻转
                    ThisUsbDev.HIDEndPoint = endp; // 保存同步标志位

                    // 比较按键前后变化，输出有变化的按键
                    if (ps2_dev) {
                        // 解析InData,记录所有按键状态
                        hid_parseInDataKB();
                        s = ps2_checkChangeKB();
                    } else {
                        // 解析InData,记录所有按键状态
                        hid_parseInDataMS();
                        s = ps2_checkChangeMS();
                    }
                    if (s) {
                        wake_timeout = 120000;
                    }
                }
            }

            if (ps2_dev) {
                // 检查机打按键，若到时则发送
                ps2_kb_checkRepeatKey();
            }
        } else {
            // 开启防睡眠功能有3个条件：
            // 1. USB端未接入设备
            // 2. 开关状态和启动时不一样
            // 3. 2分钟没有键盘或鼠标操作
            if (wake_timeout == 0) {
                wake_timeout = 120000;
                if (ps2_dev != sel_mode) {
                    if (ps2_dev) {
                        // 键盘：发送[NUm Lock]的通码和断码
                        ps2_copy_code(82, 1);
                        ps2_copy_code(82, 0);
                    } else {
                        // 鼠标：发送移动数据
                        // 右移5单位
                        PS2_TXBuffer[PS2_TX_E++] = 0x08;
                        PS2_TXBuffer[PS2_TX_E++] = 0x05;
                        PS2_TXBuffer[PS2_TX_E++] = 0x00;
                        if (PS2_MS_INFO.devID != 0x00) {
                            PS2_TXBuffer[PS2_TX_E++] = 0x00;
                        }
                        // 左移5单位
                        PS2_TXBuffer[PS2_TX_E++] = 0x18;
                        PS2_TXBuffer[PS2_TX_E++] = 0xFB;
                        PS2_TXBuffer[PS2_TX_E++] = 0x00;
                        if (PS2_MS_INFO.devID != 0x00) {
                            PS2_TXBuffer[PS2_TX_E++] = 0x00;
                        }
                    }
                }
            }
        }

        // PS/2: 发送缓存
        ps2_sendData();

        // PS/2: 接收和执行指令
        if (ps2_dev) {
            ps2_readAndExec_KB();
        } else {
            ps2_readAndExec_MS();
        }
    }
}

/********************************************************************
 * 函 数 名       : putchar
 * 函数功能       : 将printf映射到串口通讯
 * 输    入      : 字符串
 * 输    出    	: 字符串
 ********************************************************************/
int putchar(int a) {
    CH549UART1SendByte(a); // printf映射到串口1输出
    return (a);
}